<!DOCTYPE html>
<html lang="zh-CN">
  <head>
    <meta charset="utf-8" />
    <title>net-monitor WS 探测客户端</title>
    <style>
      body {
        font-family: system-ui, -apple-system, BlinkMacSystemFont, "Segoe UI",
          sans-serif;
        margin: 2rem auto;
        max-width: 960px;
        line-height: 1.6;
      }
      fieldset {
        border: 1px solid #ccc;
        padding: 1rem;
        margin-bottom: 1rem;
      }
      label {
        display: inline-block;
        min-width: 120px;
      }
      textarea,
      input,
      select,
      button {
        font-size: 14px;
      }
      textarea {
        width: 100%;
        height: 220px;
        margin-top: 0.5rem;
      }
      pre {
        background: #1e1e1e;
        color: #dcdcdc;
        padding: 1rem;
        overflow: auto;
        border-radius: 6px;
      }
      .inline {
        display: flex;
        gap: 1rem;
        flex-wrap: wrap;
        align-items: center;
      }
      .inline label {
        min-width: auto;
      }
      .status {
        font-weight: bold;
      }
    </style>
  </head>
  <body>
    <h1>net-monitor WebSocket 探测客户端</h1>
    <p>
      通过本页面可连接 `net-monitor` 的 <code>/ws/probe</code> 接口，发送
      Ping / TCPing / DNS / 端口扫描等探测指令。
    </p>
    <fieldset>
      <legend>连接设置</legend>
      <div class="inline">
        <label for="wsUrl">WebSocket URL</label>
        <input
          id="wsUrl"
          value="ws://localhost:8080/ws/probe"
          size="40"
          placeholder="ws://host:port/ws/probe"
        />
        <label for="token">token</label>
        <input id="token" value="your-secret-token" placeholder="可为空" />
        <button id="connectBtn">连接探测接口</button>
        <button id="disconnectBtn" disabled>断开探测接口</button>
        <span class="status" id="status">探测：未连接</span>
      </div>
    </fieldset>

    <fieldset>
      <legend>监控订阅</legend>
      <div class="inline">
        <label for="monitorUrl">监控 WebSocket URL</label>
        <input
          id="monitorUrl"
          value="ws://localhost:8080/ws"
          size="40"
          placeholder="ws://host:port/ws"
        />
        <label for="monitorToken">token</label>
        <input id="monitorToken" value="your-secret-token" placeholder="可为空" />
        <button id="monitorConnectBtn">连接监控</button>
        <button id="monitorDisconnectBtn" disabled>断开监控</button>
        <span class="status" id="monitorStatus">监控：未连接</span>
      </div>
      <div style="margin-top: 0.5rem;">
        <label for="monitorInterval">刷新显示间隔(秒)</label>
        <input id="monitorInterval" type="number" value="1" min="1" />
      </div>
      <pre id="monitorLog"></pre>
    </fieldset>

    <fieldset>
      <legend>发送指令</legend>
      <div class="inline">
        <label for="action">action</label>
        <select id="action">
          <option value="ping">ping</option>
          <option value="tcping">tcping</option>
          <option value="dns">dns</option>
          <option value="portscan">portscan</option>
          <option value="http_bench">http_bench</option>
          <option value="ws_bench">ws_bench</option>
        </select>

        <label for="target">target/domain</label>
        <input id="target" value="223.5.5.5" size="24" />

        <label for="port">port</label>
        <input id="port" type="number" value="80" min="1" max="65535" />

        <label for="count">count/messages</label>
        <input id="count" type="number" value="4" min="1" />

        <label for="timeout">timeout(ms)</label>
        <input id="timeout" type="number" value="1000" min="1" />

        <label for="ports">ports</label>
        <input id="ports" value="22,80,443" size="20" />

        <label for="recordType">record_type</label>
        <input id="recordType" value="A" size="6" />

        <label for="requests">requests/connections</label>
        <input id="requests" type="number" value="100" min="1" />

        <label for="benchConcurrency">bench concurrency</label>
        <input id="benchConcurrency" type="number" value="10" min="1" />

        <label for="payloadText">payload</label>
        <input
          id="payloadText"
          placeholder="WS bench 文本消息，可选"
          size="24"
        />
      </div>
      <div style="margin-top: 1rem;">
        <button id="sendBtn" disabled>发送</button>
        <button id="clearBtn">清空响应</button>
      </div>
    </fieldset>

    <fieldset>
      <legend>原始 JSON 指令</legend>
      <textarea
        id="rawPayload"
        placeholder='{"action":"ping","target":"223.5.5.5","count":4}'
      ></textarea>
      <div style="margin-top: 0.5rem;">
        <button id="sendRawBtn" disabled>发送自定义 JSON</button>
      </div>
    </fieldset>

    <fieldset>
      <legend>响应输出</legend>
      <pre id="log"></pre>
    </fieldset>

    <script>
      const statusEl = document.getElementById("status");
      const logEl = document.getElementById("log");
      const connectBtn = document.getElementById("connectBtn");
      const disconnectBtn = document.getElementById("disconnectBtn");
      const sendBtn = document.getElementById("sendBtn");
      const sendRawBtn = document.getElementById("sendRawBtn");
      const clearBtn = document.getElementById("clearBtn");

      let socket = null;
      let monitorSocket = null;
      let latestMonitorPayload = null;
      let monitorTimer = null;

      function log(message, type = "info") {
        const timestamp = new Date().toISOString();
        logEl.textContent += `[${timestamp}] (${type}) ${message}\n`;
        logEl.scrollTop = logEl.scrollHeight;
      }

      function setConnected(connected, type = "probe") {
        if (type === "probe") {
          connectBtn.disabled = connected;
          disconnectBtn.disabled = !connected;
          sendBtn.disabled = !connected;
          sendRawBtn.disabled = !connected;
          statusEl.textContent = connected ? "探测：已连接" : "探测：未连接";
          statusEl.style.color = connected ? "green" : "red";
        } else {
          document.getElementById("monitorConnectBtn").disabled = connected;
          document.getElementById("monitorDisconnectBtn").disabled = !connected;
          const monitorStatus = document.getElementById("monitorStatus");
          monitorStatus.textContent = connected ? "监控：已连接" : "监控：未连接";
          monitorStatus.style.color = connected ? "green" : "red";
        }
      }

      connectBtn.addEventListener("click", () => {
        const url = document.getElementById("wsUrl").value.trim();
        const token = document.getElementById("token").value.trim();
        const finalUrl =
          token.length > 0 ? `${url}?token=${encodeURIComponent(token)}` : url;

        if (!url) {
          alert("请填写 WebSocket URL");
          return;
        }

        socket = new WebSocket(finalUrl);

        socket.addEventListener("open", () => {
          log(`探测连接成功：${finalUrl}`);
          setConnected(true, "probe");
        });

        socket.addEventListener("close", (evt) => {
          log(`探测连接关闭：code=${evt.code}, reason=${evt.reason}`);
          setConnected(false, "probe");
        });

        socket.addEventListener("error", (evt) => {
          log(`探测连接出错：${evt.message || "unknown"}`, "error");
        });

        socket.addEventListener("message", (evt) => {
          try {
            const data = JSON.parse(evt.data);
            log(JSON.stringify(data, null, 2), "recv");
          } catch (err) {
          log(evt.data, "recv");
          }
        });
      });

      disconnectBtn.addEventListener("click", () => {
        if (socket) {
          socket.close(1000, "client closed");
          socket = null;
        }
      });

      sendBtn.addEventListener("click", () => {
        if (!socket || socket.readyState !== WebSocket.OPEN) {
          alert("WebSocket 未连接");
          return;
        }

        const action = document.getElementById("action").value;
        const payload = buildPayload(action);
        log(`发送：${JSON.stringify(payload)}`, "send");
        socket.send(JSON.stringify(payload));
      });

      sendRawBtn.addEventListener("click", () => {
        if (!socket || socket.readyState !== WebSocket.OPEN) {
          alert("WebSocket 未连接");
          return;
        }
        const raw = document.getElementById("rawPayload").value.trim();
        if (!raw) {
          alert("请填写自定义 JSON 指令");
          return;
        }
        log(`发送自定义：${raw}`, "send");
        socket.send(raw);
      });

      clearBtn.addEventListener("click", () => {
        logEl.textContent = "";
      });

      function buildPayload(action) {
        const base = {
          action,
        };
        const requests =
          Number(document.getElementById("requests").value) || 100;
        const benchConcurrency =
          Number(document.getElementById("benchConcurrency").value) || 10;
        const payloadText = document.getElementById("payloadText").value;
        const timeoutValue =
          Number(document.getElementById("timeout").value) || 1000;
        switch (action) {
          case "ping":
            return {
              ...base,
              target: document.getElementById("target").value.trim(),
              count: Number(document.getElementById("count").value) || 4,
              timeout_ms: timeoutValue,
            };
          case "tcping":
            return {
              ...base,
              target: document.getElementById("target").value.trim(),
              port: Number(document.getElementById("port").value) || 1,
              count: Number(document.getElementById("count").value) || 4,
              timeout_ms: timeoutValue,
            };
          case "dns":
            return {
              ...base,
              domain: document.getElementById("target").value.trim(),
              record_type:
                document.getElementById("recordType").value.trim() || "A",
            };
          case "portscan":
            return {
              ...base,
              target: document.getElementById("target").value.trim(),
              ports: document.getElementById("ports").value.trim(),
              timeout_ms: timeoutValue || 500,
              concurrency: benchConcurrency || 50,
            };
          case "http_bench":
            return {
              ...base,
              url: document.getElementById("target").value.trim(),
              requests,
              concurrency: benchConcurrency,
              timeout_ms: timeoutValue,
            };
          case "ws_bench":
            return {
              ...base,
              url: document.getElementById("target").value.trim(),
              connections: requests,
              concurrency: benchConcurrency,
              messages: Number(document.getElementById("count").value) || 0,
              payload: payloadText.trim() || undefined,
              timeout_ms: timeoutValue,
            };
          default:
            return base;
        }
      }

      document
        .getElementById("monitorConnectBtn")
        .addEventListener("click", () => {
          const url = document.getElementById("monitorUrl").value.trim();
          const token = document.getElementById("monitorToken").value.trim();
          if (!url) {
            alert("请填写监控 WebSocket URL");
            return;
          }
          const finalUrl =
            token.length > 0 ? `${url}?token=${encodeURIComponent(token)}` : url;

          monitorSocket = new WebSocket(finalUrl);
          monitorSocket.addEventListener("open", () => {
            log(`监控连接成功：${finalUrl}`);
            setConnected(true, "monitor");
          });
          monitorSocket.addEventListener("close", (evt) => {
            log(
              `监控连接关闭：code=${evt.code}, reason=${evt.reason ?? ""}`,
              "info"
            );
            setConnected(false, "monitor");
            stopMonitorTimer();
            latestMonitorPayload = null;
            document.getElementById("monitorLog").textContent = "";
            monitorSocket = null;
          });
          monitorSocket.addEventListener("error", (evt) => {
            log(`监控连接出错：${evt.message || "unknown"}`, "error");
          });
          monitorSocket.addEventListener("message", (evt) => {
            try {
              latestMonitorPayload = JSON.parse(evt.data);
            } catch {
              latestMonitorPayload = evt.data;
            }
            ensureMonitorTimer();
          });
        });

      document
        .getElementById("monitorDisconnectBtn")
        .addEventListener("click", () => {
          if (monitorSocket) {
            monitorSocket.close(1000, "client closed");
            monitorSocket = null;
          }
        });

      function ensureMonitorTimer() {
        if (monitorTimer) {
          return;
        }
        const intervalInput = document.getElementById("monitorInterval");
        let interval = Number(intervalInput.value);
        if (!Number.isFinite(interval) || interval <= 0) {
          interval = 1;
          intervalInput.value = "1";
        }
        monitorTimer = setInterval(() => {
          if (!latestMonitorPayload) {
            return;
          }
          const monitorLog = document.getElementById("monitorLog");
          const formatted =
            typeof latestMonitorPayload === "string"
              ? latestMonitorPayload
              : JSON.stringify(latestMonitorPayload, null, 2);
          monitorLog.textContent = formatted;
        }, interval * 1000);
      }

      function stopMonitorTimer() {
        if (monitorTimer) {
          clearInterval(monitorTimer);
          monitorTimer = null;
        }
      }
    </script>
  </body>
</html>

